/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.portlet.dao.trans;

import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apereo.portal.IUserPreferencesManager;
import org.apereo.portal.layout.IUserLayoutManager;
import org.apereo.portal.layout.TransientUserLayoutManagerWrapper;
import org.apereo.portal.layout.node.IUserLayoutChannelDescription;
import org.apereo.portal.portlet.dao.IPortletEntityDao;
import org.apereo.portal.portlet.om.IPortletDefinition;
import org.apereo.portal.portlet.om.IPortletDefinitionId;
import org.apereo.portal.portlet.om.IPortletEntity;
import org.apereo.portal.portlet.om.IPortletEntityId;
import org.apereo.portal.portlet.registry.IPortletDefinitionRegistry;
import org.apereo.portal.url.IPortalRequestUtils;
import org.apereo.portal.user.IUserInstance;
import org.apereo.portal.user.IUserInstanceManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;

/**
 * Handles entity management for transient portlets, defined as portlets that aren't permanent parts
 * of the user's layout. Portlet preferences are still persisted but are associated with a transient
 * rendering of the portlet. Transient portlets are detected by checking the channel subscribe ID
 * against the {@link TransientUserLayoutManagerWrapper#SUBSCRIBE_PREFIX} prefix.
 */
@Repository
@Qualifier("transient")
public class TransientPortletEntityDao implements IPortletEntityDao {
    protected final Log logger = LogFactory.getLog(this.getClass());

    private IPortletDefinitionRegistry portletDefinitionRegistry;
    private IPortletEntityDao delegatePortletEntityDao;
    private IUserInstanceManager userInstanceManager;
    private IPortalRequestUtils portalRequestUtils;

    /** The IPortletEntityDao to delegate calls to for actual persistence */
    @Autowired
    public void setDelegatePortletEntityDao(
            @Qualifier("persistence") IPortletEntityDao delegatePortletEntityDao) {
        this.delegatePortletEntityDao = delegatePortletEntityDao;
    }

    /** Registry for looking up data related to portlet definitions */
    @Autowired
    public void setPortletDefinitionRegistry(IPortletDefinitionRegistry portletDefinitionRegistry) {
        this.portletDefinitionRegistry = portletDefinitionRegistry;
    }

    /** Used to get access to the user's layout manager */
    @Autowired
    public void setUserInstanceManager(IUserInstanceManager userInstanceManager) {
        this.userInstanceManager = userInstanceManager;
    }

    /** Used to get access to the current portal request */
    @Autowired
    public void setPortalRequestUtils(IPortalRequestUtils portalRequestUtils) {
        this.portalRequestUtils = portalRequestUtils;
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.dao.IPortletEntityDao#createPortletEntity(org.apereo.portal.portlet.om.IPortletDefinitionId, java.lang.String, int)
     */
    @Override
    public IPortletEntity createPortletEntity(
            IPortletDefinitionId portletDefinitionId, String layoutNodeId, int userId) {
        if (layoutNodeId.startsWith(TransientUserLayoutManagerWrapper.SUBSCRIBE_PREFIX)) {
            final String transientLayoutNodeId = layoutNodeId;
            layoutNodeId = this.getPersistentLayoutNodeId(portletDefinitionId);

            final IPortletEntity portletEntity =
                    this.delegatePortletEntityDao.createPortletEntity(
                            portletDefinitionId, layoutNodeId, userId);
            return new TransientPortletEntity(portletEntity, transientLayoutNodeId);
        }

        return this.delegatePortletEntityDao.createPortletEntity(
                portletDefinitionId, layoutNodeId, userId);
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.dao.IPortletEntityDao#deletePortletEntity(org.apereo.portal.portlet.om.IPortletEntity)
     */
    @Override
    public void deletePortletEntity(IPortletEntity portletEntity) {
        portletEntity = this.unwrapEntity(portletEntity);
        this.delegatePortletEntityDao.deletePortletEntity(portletEntity);
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.dao.IPortletEntityDao#getPortletEntities(org.apereo.portal.portlet.om.IPortletDefinitionId)
     */
    @Override
    public Set<IPortletEntity> getPortletEntities(IPortletDefinitionId portletDefinitionId) {
        final Set<IPortletEntity> portletEntities =
                this.delegatePortletEntityDao.getPortletEntities(portletDefinitionId);
        return this.wrapEntities(portletEntities);
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.dao.IPortletEntityDao#getPortletEntitiesForUser(int)
     */
    @Override
    public Set<IPortletEntity> getPortletEntitiesForUser(int userId) {
        final Set<IPortletEntity> portletEntities =
                this.delegatePortletEntityDao.getPortletEntitiesForUser(userId);
        return this.wrapEntities(portletEntities);
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.dao.IPortletEntityDao#getPortletEntity(org.apereo.portal.portlet.om.IPortletEntityId)
     */
    @Override
    public IPortletEntity getPortletEntity(IPortletEntityId portletEntityId) {
        final IPortletEntity portletEntity =
                this.delegatePortletEntityDao.getPortletEntity(portletEntityId);
        return this.wrapEntity(portletEntity);
    }

    @Override
    public boolean portletEntityExists(IPortletEntityId portletEntityId) {
        return this.delegatePortletEntityDao.portletEntityExists(portletEntityId);
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.dao.IPortletEntityDao#getPortletEntity(java.lang.String, int)
     */
    @Override
    public IPortletEntity getPortletEntity(String layoutNodeId, int userId) {
        if (layoutNodeId.startsWith(TransientUserLayoutManagerWrapper.SUBSCRIBE_PREFIX)) {
            final String databaseChannelSubscribeId =
                    this.determineDatabaseChannelSubscribeId(layoutNodeId);

            final IPortletEntity portletEntity =
                    this.delegatePortletEntityDao.getPortletEntity(
                            databaseChannelSubscribeId, userId);
            return this.wrapEntity(portletEntity);
        }

        return this.delegatePortletEntityDao.getPortletEntity(layoutNodeId, userId);
    }

    /* (non-Javadoc)
     * @see org.apereo.portal.portlet.dao.IPortletEntityDao#updatePortletEntity(org.apereo.portal.portlet.om.IPortletEntity)
     */
    @Override
    public void updatePortletEntity(IPortletEntity portletEntity) {
        portletEntity = this.unwrapEntity(portletEntity);
        this.delegatePortletEntityDao.updatePortletEntity(portletEntity);
    }

    /**
     * Returns the unwrapped entity if it is an instance of TransientPortletEntity. If not the
     * original entity is returned.
     */
    protected IPortletEntity unwrapEntity(IPortletEntity portletEntity) {
        if (portletEntity instanceof TransientPortletEntity) {
            return ((TransientPortletEntity) portletEntity).getDelegatePortletEntity();
        }

        return portletEntity;
    }

    /**
     * Adds a TransientPortletEntity wrapper to the portletEntity if it is needed. If the specified
     * entity is transient but no transient subscribe id has been registered for it yet in the
     * transientIdMap null is returned. If no wrapping is needed the original entity is returned.
     */
    protected IPortletEntity wrapEntity(IPortletEntity portletEntity) {
        if (portletEntity == null) {
            return null;
        }

        final String persistentLayoutNodeId = portletEntity.getLayoutNodeId();
        if (persistentLayoutNodeId.startsWith(TransientUserLayoutManagerWrapper.SUBSCRIBE_PREFIX)) {
            final IUserLayoutManager userLayoutManager = this.getUserLayoutManager();
            if (userLayoutManager == null) {
                this.logger.warn(
                        "Could not find IUserLayoutManager when trying to wrap transient portlet entity: "
                                + portletEntity);
                return portletEntity;
            }

            final IPortletDefinition portletDefinition = portletEntity.getPortletDefinition();
            final String fname = portletDefinition.getFName();
            final String layoutNodeId = userLayoutManager.getSubscribeId(fname);
            return new TransientPortletEntity(portletEntity, layoutNodeId);
        }

        return portletEntity;
    }

    /**
     * Calles {@link #wrapEntity(IPortletEntity)} on each entry in the Set, wrap calls that return
     * null result in the entitiy being dropped from the returned Set
     */
    protected Set<IPortletEntity> wrapEntities(Set<IPortletEntity> portletEntities) {
        final Set<IPortletEntity> wrappedPortletEntities =
                new LinkedHashSet<IPortletEntity>(portletEntities.size());

        for (final IPortletEntity portletEntity : portletEntities) {
            final IPortletEntity wrappedEntity = this.wrapEntity(portletEntity);

            if (wrappedEntity != null) {
                wrappedPortletEntities.add(wrappedEntity);
            }
        }

        return wrappedPortletEntities;
    }

    protected String determineDatabaseChannelSubscribeId(String layoutNodeId) {
        // Find the referenced Node in the user's layout
        final IUserLayoutManager userLayoutManager = this.getUserLayoutManager();
        final IUserLayoutChannelDescription channelNode =
                (IUserLayoutChannelDescription) userLayoutManager.getNode(layoutNodeId);

        // Lookup the IportletDefinition for the node
        final String portletPublishId = channelNode.getChannelPublishId();
        final IPortletDefinition portletDefinition =
                this.portletDefinitionRegistry.getPortletDefinition(portletPublishId);

        // Generate the subscribe ID used for the database
        return this.getPersistentLayoutNodeId(portletDefinition.getPortletDefinitionId());
    }

    protected String getPersistentLayoutNodeId(IPortletDefinitionId portletDefinitionId) {
        return TransientUserLayoutManagerWrapper.SUBSCRIBE_PREFIX
                + "."
                + portletDefinitionId.getStringId();
    }

    protected IUserLayoutManager getUserLayoutManager() {
        final HttpServletRequest portalRequest = this.portalRequestUtils.getCurrentPortalRequest();
        final IUserInstance userInstance = this.userInstanceManager.getUserInstance(portalRequest);
        final IUserPreferencesManager preferencesManager = userInstance.getPreferencesManager();
        return preferencesManager.getUserLayoutManager();
    }
}
